/*
 * Copyright 2011 yingxinwu.g@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package xink.vpn.wrapper;

import java.lang.reflect.Method;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
import dalvik.system.PathClassLoader;

public abstract class AbstractWrapper implements Cloneable {

	/**
	 * subclass to customize stub object creation
	 */
	protected static class StubInstanceCreator {
		protected Object newStubInstance(final Class<?> stubClass,
				final Context context) throws Exception {
			return stubClass.newInstance();
		}
	}

	private static final String STUB_PACK = "com.android.settings";

	private static PathClassLoader stubClassLoader;

	private transient Context context;

	private String stubClassName;

	private Class<?> stubClass;

	private Object stub;

	private StubInstanceCreator stubInstanceCreator;

	protected AbstractWrapper(final Context ctx, final String stubClass) {
		super();
		this.context = ctx;
		stubClassName = stubClass;
		stubInstanceCreator = new StubInstanceCreator();
		init();
	}

	protected AbstractWrapper(final Context ctx, final String stubClass,
			final StubInstanceCreator stubCreator) {
		super();
		this.context = ctx;
		stubClassName = stubClass;
		stubInstanceCreator = stubCreator;
		init();
	}

	public Context getContext() {
		return context;
	}

	public String getStubClassName() {
		return stubClassName;
	}

	public Class<?> getStubClass() {
		return stubClass;
	}

	public void setStub(final Object stub) {
		this.stub = stub;
	}

	public Object getStub() {
		return stub;
	}

	private void init() {
		try {
			initClassLoader(context);
			stubClass = loadClass(stubClassName);
			stub = stubInstanceCreator.newStubInstance(stubClass, context);
		} catch (Exception e) {
			throw new WrapperException("init classloader failed", e);
		}
	}

	private static void initClassLoader(final Context ctx)
			throws NameNotFoundException {
		if (stubClassLoader != null) {
			return;
		}
		ApplicationInfo vpnAppInfo = ctx.getPackageManager()
				.getApplicationInfo(STUB_PACK, 0);
		stubClassLoader = new PathClassLoader(vpnAppInfo.sourceDir,
				ClassLoader.getSystemClassLoader());
	}

	protected static final Class<?> loadClass(final String qname)
			throws ClassNotFoundException {
		return Class.forName(qname, true, stubClassLoader);
	}

	@SuppressWarnings("unchecked")
	protected <T> T invokeStubMethod(final String methodName,
			final Object... args) {
		try {
			Method method = findStubMethod(methodName, args);
			return (T) method.invoke(stub, args);
		} catch (Exception e) {
			throw new WrapperException("failed to invoke mehod '" + methodName
					+ "' on stub", e);
		}
	}

	protected Method findStubMethod(final String methodName,
			final Object... args) throws NoSuchMethodException {
		return findMethod(stubClass, methodName, args);
	}

	protected Method findMethod(final Class<?> clazz, final String methodName,
			final Object... args) throws NoSuchMethodException {
		Class<?>[] argTypes = new Class<?>[args.length];
		int i = 0;
		for (Object arg : args) {
			argTypes[i++] = arg.getClass();
		}

		Method method = clazz.getMethod(methodName, argTypes);
		method.setAccessible(true);
		return method;
	}

	@Override
	public AbstractWrapper clone() {
		AbstractWrapper c = null;
		try {
			c = (AbstractWrapper) super.clone();
			c.init();
		} catch (CloneNotSupportedException e) {
			Log.e("xink", "clone failed", e);
		}
		return c;
	}
}
